/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/device.h>
#include <asm/unaligned.h>
#include <linux/mutex.h>
#include <linux/slab.h>

#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/cm.h>

#include "log.h"

#include "interpreter.h"

#include "tables/atb.h"
#include "tables/cmd.h"
#include "tables/data.h"
#include "tables/ss_info.h"
#include "tables/firmware_info.h"
#include "tables/i2c.h"
#include "tables/pp.h"
#include "tables/pll_compute.h"
#include "tables/eng_pll_compute.h"
#include "tables/mem_pll_compute.h"

#include "atb.h"

static inline u32 set(u32 mask, u32 v)
{
	u8 shift;

	shift = ffs(mask) - 1;
	return (v << shift) & mask;
}

static inline u32 get(u32 mask, u32 v)
{
	u8 shift;

	shift = ffs(mask) - 1;
	return (v & mask) >> shift;
}

#define FIRMWARE_INFO_DONT_HAVE_ENG_SS	0
#define FIRMWARE_INFO_HAVE_ENG_SS	1
static long have_eng_clk_ss(struct atombios *atb)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct firmware_info_v2_2 *info;
	long r;
	u16 caps;

	/* engine and memory default clocks */
	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.firmware_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:firmware_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 2 && info->hdr.tbl_content_rev != 2) {
		dev_err(atb->adev.dev, "atombios:firmware_info revision not supported");
		r = -ATB_ERR;
		goto exit;
	}

	caps = get_unaligned_le16(&info->caps);
	if (caps & FIRMWARE_INFO_ENG_SS)
		r = FIRMWARE_INFO_HAVE_ENG_SS;
	else
		r = FIRMWARE_INFO_DONT_HAVE_ENG_SS;
exit:
	return r;
}

#define FIRMWARE_INFO_DONT_HAVE_MEM_SS	0
#define FIRMWARE_INFO_HAVE_MEM_SS	1
static long have_mem_clk_ss(struct atombios *atb)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct firmware_info_v2_2 *info;
	long r;
	u16 caps;

	/* engine and memory default clocks */
	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.firmware_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:firmware_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 2 && info->hdr.tbl_content_rev != 2) {
		dev_err(atb->adev.dev, "atombios:firmware_info revision not supported");
		r = -ATB_ERR;
		goto exit;
	}

	caps = get_unaligned_le16(&info->caps);
	if (caps & FIRMWARE_INFO_MEM_SS)
		r = FIRMWARE_INFO_HAVE_MEM_SS;
	else
		r = FIRMWARE_INFO_DONT_HAVE_MEM_SS;
exit:
	return r;
}

long atb_have_mem_clk_ss(struct atombios *atb)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct ss_info *info;
	long r;
	u16 tbl_sz;
	u8 entries_n;
	u8 i;

	mutex_lock(&atb->mutex);

	/* first, check the major switch in firmware info */
	r = have_mem_clk_ss(atb);
	if (r == -ATB_ERR)
		goto unlock_mutex;
	if (r == FIRMWARE_INFO_DONT_HAVE_MEM_SS) {
		r = ATB_DONT_HAVE_MEM_CLK_SS;
		goto unlock_mutex;
	}
		
	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.ss_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:ss_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 3) {
		dev_err(atb->adev.dev, "atombios:ss_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	r = ATB_DONT_HAVE_MEM_CLK_SS;

	tbl_sz = get_unaligned_le16(&info->hdr.sz);
	entries_n = (tbl_sz - sizeof(info->hdr)) / sizeof(info->entries[0]);

	for (i = 0; i < entries_n; ++i)
		if (info->entries[i].clk_id == SS_INFO_ENTRY_CLK_ID_MEM) {
			r = ATB_HAVE_MEM_CLK_SS;
			break;
		}
unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_have_mem_clk_ss);

long atb_have_eng_clk_ss(struct atombios *atb)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct ss_info *info;
	long r;
	u16 tbl_sz;
	u8 entries_n;
	u8 i;

	mutex_lock(&atb->mutex);

	/* first, check the major switch in firmware info */
	r = have_eng_clk_ss(atb);
	if (r == -ATB_ERR)
		goto unlock_mutex;
	if (r == FIRMWARE_INFO_DONT_HAVE_ENG_SS) {
		r = ATB_DONT_HAVE_ENG_CLK_SS;
		goto unlock_mutex;
	}

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.ss_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:ss_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 3) {
		dev_err(atb->adev.dev, "atombios:ss_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	r = ATB_DONT_HAVE_ENG_CLK_SS;

	tbl_sz = get_unaligned_le16(&info->hdr.sz);
	entries_n = (tbl_sz - sizeof(info->hdr)) / sizeof(info->entries[0]);

	for (i = 0; i < entries_n; ++i)
		if (info->entries[i].clk_id == SS_INFO_ENTRY_CLK_ID_ENG) {
			r = ATB_HAVE_ENG_CLK_SS;
			break;
		}

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_have_eng_clk_ss);

long atb_eng_clk_ss_get(struct atombios *atb, u32 eng_clk,
							struct atb_ss *atb_ss)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct ss_info *info;
	long r;
	u16 tbl_sz;
	u8 entries_n;
	u8 i;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.ss_info);
	info = atb->adev.rom + of;

	DEV_INFOC(atb->adev.dev, "ss_info (0x%04x) revision %u.%u", of,
			info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 3) {
		dev_err(atb->adev.dev, "atombios:ss_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	r = -ATB_ERR;

	tbl_sz = get_unaligned_le16(&info->hdr.sz);
	entries_n = (tbl_sz - sizeof(info->hdr)) / sizeof(info->entries[0]);

	for (i = 0; i < entries_n; ++i) {
		struct ss_info_entry *entry;

		entry = &info->entries[i];

		if (entry->clk_id == SS_INFO_ENTRY_CLK_ID_ENG
			&& eng_clk < get_unaligned_le32(&entry->tgt_clk_rng)) {

			memset(atb_ss, 0, sizeof(*atb_ss));

			atb_ss->percentage = get_unaligned_le16(
							&entry->ss_percentage);
			atb_ss->mode = entry->mode;

			/* convert from 10 Hz units to 10 kHz units */
			atb_ss->rate = get_unaligned_le16(&entry->spread_rate)
									/ 100;
			r = 0;
			break;
		}
	}

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_eng_clk_ss_get);

long atb_mem_clk_ss_get(struct atombios *atb, u32 mem_clk,
							struct atb_ss *atb_ss)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct ss_info *info;
	long r;
	u16 tbl_sz;
	u8 entries_n;
	u8 i;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.ss_info);
	info = atb->adev.rom + of;

	DEV_INFOC(atb->adev.dev, "ss_info (0x%04x) revision %u.%u", of,
			info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 3) {
		dev_err(atb->adev.dev, "atombios:ss_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	r = -ATB_ERR;

	tbl_sz = get_unaligned_le16(&info->hdr.sz);
	entries_n = (tbl_sz - sizeof(info->hdr)) / sizeof(info->entries[0]);

	for (i = 0; i < entries_n; ++i) {
		struct ss_info_entry *entry;

		entry = &info->entries[i];

		if (entry->clk_id == SS_INFO_ENTRY_CLK_ID_MEM
			&& mem_clk < get_unaligned_le32(&entry->tgt_clk_rng)) {

			memset(atb_ss, 0, sizeof(*atb_ss));

			atb_ss->percentage = get_unaligned_le16(
							&entry->ss_percentage);
			atb_ss->mode = entry->mode;

			/* convert from 10 Hz units to 10 kHz units */
			atb_ss->rate = get_unaligned_le16(&entry->spread_rate)
									/ 100;
			r = 0;
			break;
		}
	}

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_mem_clk_ss_get);

long atb_core_ref_clk_get(struct atombios *atb, u16 *core_ref_clk)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct firmware_info_v2_2 *info;
	long r;

	/* engine and memory default clocks */
	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.firmware_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:firmware_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 2 && info->hdr.tbl_content_rev != 2) {
		dev_err(atb->adev.dev, "atombios:firmware_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	*core_ref_clk = get_unaligned_le16(&info->core_ref_clk);
	r = 0;

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_core_ref_clk_get);

long atb_mem_ref_clk_get(struct atombios *atb, u16 *mem_ref_clk)
{
	u16 of;
	struct master_data_tbl *data_tbl;
	struct firmware_info_v2_2 *info;
	long r;

	/* engine and memory default clocks */
	of = get_unaligned_le16(&atb->hdr->master_data_tbl_of);
	data_tbl = atb->adev.rom + of;

	of = get_unaligned_le16(&data_tbl->list.firmware_info);
	info = atb->adev.rom + of;

	dev_info(atb->adev.dev, "atombios:firmware_info (0x%04x) revision %u.%u\n",
			of, info->hdr.tbl_fmt_rev, info->hdr.tbl_content_rev);
	if (info->hdr.tbl_fmt_rev != 2 && info->hdr.tbl_content_rev != 2) {
		dev_err(atb->adev.dev, "atombios:firmware_info revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	*mem_ref_clk = get_unaligned_le16(&info->mem_ref_clk);
	r = 0;

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_mem_ref_clk_get);

long atb_eng_clk_set(struct atombios *atb, u32 clk)
{
	u16 of;
	struct master_cmd_tbl *cmd_tbl;
	struct common_cmd_tbl_hdr *eng_clk_set;
	struct mem_eng_clk_set_params *ps;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_cmd_tbl_of);
	cmd_tbl = atb->adev.rom + of;
	of = get_unaligned_le16(&cmd_tbl->list.eng_clk_set);

	eng_clk_set = atb->adev.rom + of;
	dev_info(atb->adev.dev, "atombios:eng_clk_set (0x%04x) revision %u.%u\n",
					of, eng_clk_set->hdr.tbl_fmt_rev,
					eng_clk_set->hdr.tbl_content_rev);

	atb->g_ctx.ps_dws = 0x80;/* max for ps index */
	atb->g_ctx.ps_top = kzalloc(atb->g_ctx.ps_dws * 4, GFP_KERNEL);
	if (!atb->g_ctx.ps_top) {
		dev_err(atb->adev.dev, "atombios:unable to allocate parameter space (stack)\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}
	ps = (struct mem_eng_clk_set_params *)atb->g_ctx.ps_top;
	put_unaligned_le32(clk, &ps->clk);

	atb->g_ctx.fb_wnd = 0;
	atb->g_ctx.regs_blk = 0;
	atb->g_ctx.io_mode = IO_MM;

	r = interpret(atb, of, 0, 0);
	kfree(atb->g_ctx.ps_top);

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_eng_clk_set);

long atb_mem_clk_set(struct atombios *atb, u32 clk)
{
	u16 of;
	struct master_cmd_tbl *cmd_tbl;
	struct common_cmd_tbl_hdr *mem_clk_set;
	struct mem_eng_clk_set_params *ps;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_cmd_tbl_of);
	cmd_tbl = atb->adev.rom + of;
	of = get_unaligned_le16(&cmd_tbl->list.mem_clk_set);

	mem_clk_set = atb->adev.rom + of;
	dev_info(atb->adev.dev, "atombios:mem_clk_set (0x%04x) revision %u.%u\n",
					of, mem_clk_set->hdr.tbl_fmt_rev,
					mem_clk_set->hdr.tbl_content_rev);

	atb->g_ctx.ps_dws = 0x80;/* max for ps index */
	atb->g_ctx.ps_top = kzalloc(atb->g_ctx.ps_dws * 4, GFP_KERNEL);
	if (!atb->g_ctx.ps_top) {
		dev_err(atb->adev.dev, "atombios:unable to allocate parameter space (stack)\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}
	ps = (struct mem_eng_clk_set_params *)atb->g_ctx.ps_top;
	put_unaligned_le32(clk, &ps->clk);

	atb->g_ctx.fb_wnd = 0;
	atb->g_ctx.regs_blk = 0;
	atb->g_ctx.io_mode = IO_MM;

	r = interpret(atb, of, 0, 0);
	kfree(atb->g_ctx.ps_top);

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_mem_clk_set);

long atb_eng_mem_timings_program(struct atombios *atb, u32 eng_clk,
								u32 mem_clk)
{
	u16 of;
	struct master_cmd_tbl *cmd_tbl;
	struct common_cmd_tbl_hdr *dyn_mem_settings;
	struct mem_eng_clk_set_params *ps;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_cmd_tbl_of);
	cmd_tbl = atb->adev.rom + of;
	of = get_unaligned_le16(&cmd_tbl->list.dyn_mem_settings);

	dyn_mem_settings = atb->adev.rom + of;
	dev_info(atb->adev.dev, "atombios:dyn_mem_settings (0x%04x) revision %u.%u\n",
					of,dyn_mem_settings->hdr.tbl_fmt_rev,
					dyn_mem_settings->hdr.tbl_content_rev);
	/* FIXME: lock a revision */

	atb->g_ctx.ps_dws = 0x80;/* max for ps index */
	atb->g_ctx.ps_top = kzalloc(atb->g_ctx.ps_dws * 4, GFP_KERNEL);
	if (!atb->g_ctx.ps_top) {
		dev_err(atb->adev.dev, "atombios:unable to allocate parameter space (stack)\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}
	ps = (struct mem_eng_clk_set_params *)atb->g_ctx.ps_top;
	eng_clk |= set(MEM_ENG_COMPUTE_OP, COMPUTE_ENGINE_PLL);
	put_unaligned_le32(eng_clk, &ps->clk);
	put_unaligned_le32(mem_clk, &ps->mem_eng_pll_compute_params.mem_clk);

	atb->g_ctx.fb_wnd = 0;
	atb->g_ctx.regs_blk = 0;
	atb->g_ctx.io_mode = IO_MM;

	r = interpret(atb, of, 0, 0);
	kfree(atb->g_ctx.ps_top);

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_eng_mem_timings_program);

long atb_eng_pll_compute(struct atombios *atb, u32 eng_clk,
						struct atb_eng_pll *atb_eng_pll)
{
	u16 of;
	struct master_cmd_tbl *cmd_tbl;
	struct common_cmd_tbl_hdr *eng_pll_compute;
	struct eng_pll_compute_params *ps;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_cmd_tbl_of);
	cmd_tbl = atb->adev.rom + of;
	of = get_unaligned_le16(&cmd_tbl->list.eng_pll_compute);

	eng_pll_compute = atb->adev.rom + of;
	DEV_INFOC(atb->adev.dev, "eng_pll_compute(0x%04x) revision %u.%u",
					of,eng_pll_compute->hdr.tbl_fmt_rev,
					eng_pll_compute->hdr.tbl_content_rev);
	if (eng_pll_compute->hdr.tbl_fmt_rev != 1
				&& eng_pll_compute->hdr.tbl_content_rev != 5) {
		dev_err(atb->adev.dev, "atombios:eng_pll_compute revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	atb->g_ctx.ps_dws = 0x80;/* max for ps index */
	atb->g_ctx.ps_top = kzalloc(atb->g_ctx.ps_dws * 4, GFP_KERNEL);
	if (!atb->g_ctx.ps_top) {
		dev_err(atb->adev.dev, "atombios:eng_pll_compute unable to allocate parameter space (stack)\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}
	ps = (struct eng_pll_compute_params*)atb->g_ctx.ps_top;
	eng_clk |= set(MEM_ENG_COMPUTE_OP, COMPUTE_ENGINE_PLL);
	put_unaligned_le32(eng_clk, &ps->u.clk);

	atb->g_ctx.fb_wnd = 0;
	atb->g_ctx.regs_blk = 0;
	atb->g_ctx.io_mode = IO_MM;

	r = interpret(atb, of, 0, 0);

	if (r == 0) {
		memset(atb_eng_pll, 0, sizeof(*atb_eng_pll));

		atb_eng_pll->post_div = ps->post_div;
		if (ps->flgs & PLL_POST_DIV_ENA)
			atb_eng_pll->flgs |= ATB_ENG_PLL_POST_DIV_ENA;
		if (!(ps->flgs & FRAC_DIS))
			atb_eng_pll->flgs |= ATB_ENG_PLL_DITHEN_ENA;
		atb_eng_pll->fb_integer = get_unaligned_le16(
							&ps->u.fb_div.integer);
		atb_eng_pll->fb_frac = get_unaligned_le16(
							&ps->u.fb_div.frac);
		atb_eng_pll->ref_div = ps->ref_div;
		if (ps->flgs & MEM_PLL_VCO_MODE)
			atb_eng_pll->flgs |= ATB_ENG_PLL_VCO_MODE;
	}
	kfree(atb->g_ctx.ps_top);

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_eng_pll_compute);

long atb_mem_pll_compute(struct atombios *atb, u32 mem_clk, u8 strobe_mode,
						struct atb_mem_pll *atb_mem_pll)
{
	u16 of;
	struct master_cmd_tbl *cmd_tbl;
	struct common_cmd_tbl_hdr *mem_pll_compute;
	struct mem_pll_compute_params *ps;
	long r;

	mutex_lock(&atb->mutex);

	of = get_unaligned_le16(&atb->hdr->master_cmd_tbl_of);
	cmd_tbl = atb->adev.rom + of;
	of = get_unaligned_le16(&cmd_tbl->list.mem_pll_compute);

	mem_pll_compute = atb->adev.rom + of;
	dev_info(atb->adev.dev, "atombios:mem_pll_compute(0x%04x) revision %u.%u\n",
					of,mem_pll_compute->hdr.tbl_fmt_rev,
					mem_pll_compute->hdr.tbl_content_rev);
	if (mem_pll_compute->hdr.tbl_fmt_rev != 2 &&
				mem_pll_compute->hdr.tbl_content_rev != 1) {
		dev_err(atb->adev.dev, "atombios:mem_pll_compute revision not supported");
		r = -ATB_ERR;
		goto unlock_mutex;
	}

	atb->g_ctx.ps_dws = 0x80;/* max for ps index */
	atb->g_ctx.ps_top = kzalloc(atb->g_ctx.ps_dws * 4, GFP_KERNEL);
	if (!atb->g_ctx.ps_top) {
		dev_err(atb->adev.dev, "atombios:unable to allocate parameter space (stack)\n");
		r = -ATB_ERR;
		goto unlock_mutex;
	}
	ps = (struct mem_pll_compute_params*)atb->g_ctx.ps_top;
	put_unaligned_le32(mem_clk, &ps->u1.clk);
	if (strobe_mode)
		ps->u2.input_flgs |= INPUT_FLGS_STROBE_MODE_ENA;

	atb->g_ctx.fb_wnd = 0;
	atb->g_ctx.regs_blk = 0;
	atb->g_ctx.io_mode = IO_MM;

	r = interpret(atb, of, 0, 0);

	if (r == 0) {
		memset(atb_mem_pll, 0, sizeof(*atb_mem_pll));

		atb_mem_pll->fb_frac = get_unaligned_le16(&ps->u1.fb_div.frac);
		atb_mem_pll->fb_integer = get_unaligned_le16(
							&ps->u1.fb_div.integer);
		atb_mem_pll->post_div = ps->post_div;
		atb_mem_pll->dll_speed = ps->dll_speed;
		atb_mem_pll->bw_ctl = ps->bw_ctl;
		atb_mem_pll->vco_mode = get(PLL_CTL_VCO_MODE, ps->u2.pll_ctl);
		if (ps->u2.pll_ctl & PLL_CTL_BYPASS_DQ_PLL)
			atb_mem_pll->flgs |= ATB_MEM_PLL_Y_CLK_SEL;
		if (ps->u2.pll_ctl & PLL_CTL_QDR_ENA)
			atb_mem_pll->flgs |= ATB_MEM_PLL_QDR_ENA;
		if (ps->u2.pll_ctl & PLL_CTL_AD_HALF_RATE)
			atb_mem_pll->flgs |= ATB_MEM_PLL_AD_HALF_RATE;
	}
	kfree(atb->g_ctx.ps_top);

unlock_mutex:
	mutex_unlock(&atb->mutex);
	return r;
}
EXPORT_SYMBOL_GPL(atb_mem_pll_compute);
